﻿@page "/user/profile"

@using CleanArchitecture.Blazor.Application.Features.Fusion
@using CleanArchitecture.Blazor.Server.UI.Pages.Identity.Users.Components
@using ResizeMode = SixLabors.ImageSharp.Processing.ResizeMode
@using Size = SixLabors.ImageSharp.Size
@using SixLabors.ImageSharp.Processing
@using SixLabors.ImageSharp
@using SixLabors.ImageSharp.Formats.Png
@using CleanArchitecture.Blazor.Domain.Identity
@using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity
@using CleanArchitecture.Blazor.Server.UI.Services.JsInterop
@using CleanArchitecture.Blazor.Domain.Common.Enums
@inject IStringLocalizer<Profile> Localizer
@inject IUploadService UploadService
@inject IOnlineUserTracker OnlineUserTracker
@inject UserProfileStateService UserProfileStateService
@inject IJSRuntime JS

<PageTitle>@_title</PageTitle>

@if (_profileModel is null)
{
    <MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-7" />
}
else
{
    <MudTabs Outlined="true" Position="Position.Top" Rounded="true" Border="true" Elevation="6" ActivePanelIndexChanged="ActivePanelIndexChanged"
             ApplyEffectsToContainer="true" Class="mt-8" PanelClass="pa-6">
        <MudTabPanel Text="@Localizer["Profile"]">
            <MudForm Model="@_profileModel" @ref="_profileForm" Validation="@(Validator.ValidateValue(_profileModel))"
                     Style="display: flex; align-items: center; flex-direction: column;">
                <MudGrid Justify="Justify.Center" Spacing="2" Style="max-width:600px; display:flex;">
                    <!-- Profile Picture and Roles -->
                    <MudItem sm="12" xs="12">
                        <div class="d-flex justify-center">
                            <MudElement Class="d-flex flex-column align-center">
                                @if (string.IsNullOrEmpty(_profileModel.ProfilePictureDataUrl))
                                {
                                    <MudAvatar Style="height:120px; width:120px; font-size:2rem;">
                                        @(string.IsNullOrEmpty(_profileModel.UserName) ? "" : _profileModel.UserName.ToUpper().First())
                                    </MudAvatar>
                                }
                                else
                                {
                                    <MudAvatar Style="height:120px; width:120px; font-size:2rem;">
                                        <MudImage Src="@_profileModel.ProfilePictureDataUrl" />
                                    </MudAvatar>
                                }
                                @if (_profileModel.AssignedRoles is not null)
                                {
                                    <div class="d-flex">
                                        @foreach (var role in _profileModel.AssignedRoles)
                                        {
                                            <MudChip T="string" Size="MudBlazor.Size.Small">@role</MudChip>
                                        }
                                    </div>
                                }
                            </MudElement>
                            <MudTooltip Text="@Localizer["Click upload an image"]">
                                <MudFileUpload T="IBrowserFile" Accept=".jpg, .jpeg, .png, .webp" FilesChanged="UploadPhoto"
                                               Style="margin-top:-10px; margin-left:-15px">
                                    <ActivatorContent>
                                        <MudIconButton Icon="@Icons.Material.Filled.PhotoCamera" />
                                    </ActivatorContent>
                                </MudFileUpload>
                            </MudTooltip>
                        </div>
                    </MudItem>

                    <!-- User Information Fields -->
                    <MudItem sm="6" xs="12">
                        <MudTextField For="@(() => _profileModel.TenantName)" @bind-Value="_profileModel.TenantName" Label="@Localizer["Tenant Name"]" ReadOnly="true" />
                    </MudItem>
                    <MudItem xs="12" sm="6">
                        <MudTextField For="@(() => _profileModel.SuperiorName)" @bind-Value="_profileModel.SuperiorName" Label="@Localizer["Superior Name"]" ReadOnly="true" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <MudTextField For="@(() => _profileModel.UserName)" @bind-Value="_profileModel.UserName" Label="@Localizer["User Name"]" ReadOnly="true" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <MudTextField For="@(() => _profileModel.Email)" @bind-Value="_profileModel.Email" Label="@Localizer["Email"]" ReadOnly="true" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <MudTextField For="@(() => _profileModel.DisplayName)" @bind-Value="_profileModel.DisplayName" Label="@Localizer["Full Name"]" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <MudTextField For="@(() => _profileModel.PhoneNumber)" @bind-Value="_profileModel.PhoneNumber" Label="@Localizer["Phone Number"]" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <TimeZoneAutocomplete T="string" For="@(() => _profileModel.TimeZoneId)" @bind-Value="_profileModel.TimeZoneId" Label="@Localizer["Time Zone"]" />
                    </MudItem>
                    <MudItem sm="6" xs="12">
                        <LanguageAutocomplete T="string" For="@(() => _profileModel.LanguageCode)" @bind-Value="_profileModel.LanguageCode" Label="@Localizer["Language"]" />
                    </MudItem>
                    <MudItem sm="12" xs="12" Class="d-flex justify-end">
                        <MudButton ButtonType="ButtonType.Button" Disabled="@_submitting" Color="Color.Primary" Class="ml-auto"
                                   OnClick="@(async () => await SubmitProfileAsync())">
                            @if (_submitting)
                            {
                                <MudProgressCircular Class="ms-n1" Size="MudBlazor.Size.Small" Indeterminate="true" />
                                <MudText Class="ms-2">@ConstantString.Waiting </MudText>
                            }
                            else
                            {
                                <MudText>@ConstantString.Save</MudText>
                            }
                        </MudButton>
                    </MudItem>
                </MudGrid>
            </MudForm>
        </MudTabPanel>

        <MudTabPanel Text="@Localizer["Change Password"]">
            <MudForm Model="@_changePasswordModel" @ref="_passwordForm" Validation="@(Validator.ValidateValue(_changePasswordModel))"
                     Style="display: flex; align-items: center; flex-direction: column;">
                <MudGrid Spacing="2" Justify="Justify.Center" Style="max-width:300px">
                    <MudItem sm="12" xs="12">
                        <MudTextField InputType="InputType.Password"
                                      Label="@Localizer["Current Password"]"
                                      For="@(() => _changePasswordModel.CurrentPassword)"
                                      @bind-Value="_changePasswordModel.CurrentPassword"
                                      Required="true" />
                    </MudItem>
                    <MudItem xs="12">
                        <MudTextField InputType="InputType.Password"
                                      Label="@Localizer["New Password"]"
                                      For="@(() => _changePasswordModel.NewPassword)"
                                      @bind-Value="_changePasswordModel.NewPassword"
                                      Required="true" />
                    </MudItem>
                    <MudItem xs="12">
                        <MudTextField InputType="InputType.Password"
                                      Label="@Localizer["Confirm New Password"]"
                                      For="@(() => _changePasswordModel.ConfirmPassword)"
                                      @bind-Value="_changePasswordModel.ConfirmPassword"
                                      Required="true" />
                    </MudItem>
                    <MudItem sm="12" xs="12" Class="d-flex justify-end">
                        <MudButton ButtonType="ButtonType.Button" Color="Color.Primary" Class="ml-auto"
                                   OnClick="@(async () => await ChangePasswordAsync())">
                            @if (_submitting)
                            {
                                <MudProgressCircular Class="ms-n1" Size="MudBlazor.Size.Small" Indeterminate="true" />
                                <MudText Class="ms-2">@ConstantString.Waiting </MudText>
                            }
                            else
                            {
                                <MudText>@Localizer["Change Password"]</MudText>
                            }
                        </MudButton>
                    </MudItem>
                </MudGrid>
            </MudForm>
        </MudTabPanel>
        <MudTabPanel Text="@Localizer["Two-Factor Authentication"]">
            <div Class="pa-4" Style="max-width:600px; margin:auto;">
                <MudText Typo="Typo.h6" Class="mb-4">@Localizer["Two-Factor Authentication (2FA)"]</MudText>

                @if (_twoFactorEnabled)
                {
                    <MudAlert Severity="Severity.Success" Class="my-4">@Localizer["2FA is currently enabled."]</MudAlert>

                    <MudText Class="mb-4">@Localizer["If you lose access to your authenticator device, you can use recovery codes to log in."]</MudText>

                    <div class="d-flex gap-4 mb-4">
                        <MudButton 
                                   OnClick="@(async () => await ShowRecoveryCodesAsync())">
                            @Localizer["View Recovery Codes"]
                        </MudButton>

                        <MudButton Variant="Variant.Filled" Color="Color.Error"
                                   OnClick="@(async () => await DisableTwoFactorAsync())">
                            @Localizer["Disable 2FA"]
                        </MudButton>
                    </div>
                }
                else
                {
                    <MudAlert Severity="Severity.Info" Class="my-4">@Localizer["2FA is currently disabled."]</MudAlert>

                    @if (_showSetup)
                    {
                        <MudText Class="mb-2">@Localizer["Scan the QR code below with your authenticator app:"]</MudText>

                        <div class="d-flex justify-center my-4">
                            @if (!string.IsNullOrEmpty(_authenticatorUri))
                            {
                                <MudImage Src="@_qrCodeImageUrl" Alt="QR Code" Width="200" Height="200" ObjectFit="ObjectFit.Cover" />
                            }
                        </div>

                        <MudText Class="mb-2">@Localizer["Or enter this key manually in your app:"]</MudText>
                        <MudPaper Elevation="0" Class="pa-2 mb-4 mud-background-gray rounded">
                            <code>@_sharedKey</code>
                        </MudPaper>

                        <MudTextField @bind-Value="_verificationCode" Label="@Localizer["Verification Code"]"
                                      Variant="Variant.Outlined" Class="mb-4" Required="true" />

                        <div class="d-flex gap-2">
                            <MudButton Variant="Variant.Filled" Color="Color.Primary"
                                       OnClick="@(async () => await VerifyTwoFactorSetupAsync())"
                                       Disabled="@(_verificationCode?.Length != 6)">
                                @if (_submitting)
                                {
                                    <MudProgressCircular Class="ms-n1" Size="MudBlazor.Size.Small" Indeterminate="true" />
                                    <MudText Class="ms-2">@ConstantString.Waiting</MudText>
                                }
                                else
                                {
                                    <MudText>@Localizer["Verify"]</MudText>
                                }
                            </MudButton>

                            <MudButton Variant="Variant.Outlined" Color="Color.Secondary"
                                       OnClick="@(() => _showSetup = false)">
                                @Localizer["Cancel"]
                            </MudButton>
                        </div>
                    }
                    else
                    {
                        <MudText Class="mb-4">@Localizer["Two-factor authentication adds an extra layer of security to your account by requiring more than just a password to sign in."]</MudText>

                        <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(async () => await SetupTwoFactorAsync())">
                            @Localizer["Enable 2FA"]
                        </MudButton>
                    }
                }
            </div>
        </MudTabPanel>
        <MudTabPanel Text="@Localizer["Org Chart"]">
            <div class="chart-container" style="height: calc(100vh - 265px);"></div>
        </MudTabPanel>
    </MudTabs>
}

@code {
    #region Fields and Service Injection

    [Inject] protected IServiceProvider Services { get; init; } = null!;
    [CascadingParameter] private Task<AuthenticationState> AuthState { get; set; } = default!;

    // Private fields using underscore prefix
    private string _currentUserName = string.Empty;
    private UserManager<ApplicationUser> _userManager = null!;
    private MudForm? _profileForm;
    private MudForm? _passwordForm;
    private bool _submitting;
    private readonly List<OrgItem> _orgData = new();
    private ChangeUserProfileModel _profileModel = null!;
    private ChangePasswordModel _changePasswordModel { get; } = new();
    public string Title { get; set; } = "Profile";
    private string _title => Title;

    // 2FA fields
    private bool _twoFactorEnabled;
    private bool _showSetup;
    private string? _authenticatorUri;
    private string? _sharedKey;
    private string? _verificationCode;
    private string? _qrCodeImageUrl;
    private List<string> _recoveryCodes = new();


    #endregion

    #region Lifecycle Methods

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthState;
        _userManager = Services.GetRequiredService<UserManager<ApplicationUser>>();

        _currentUserName = authState.User.Identity?.Name ?? string.Empty;
        await UserProfileStateService.InitializeAsync(_currentUserName);
        // Map user profile data to the profile model
        _profileModel = Mapper.Map<ChangeUserProfileModel>(UserProfileStateService.UserProfile);
    }


    #endregion

    #region Organization Chart Methods

    // Triggered when the active tab changes
    private async Task ActivePanelIndexChanged(int index)
    {
        if (index == 2)
        {
            var user = await _userManager.FindByIdAsync(_profileModel.UserId);
            _twoFactorEnabled= await _userManager.GetTwoFactorEnabledAsync(user!);
        }
        else if (index == 3)
        {
            await LoadOrgChartAsync();
        }

    }

    // Load organization chart data and initialize the chart via JS interop
    private async Task LoadOrgChartAsync()
    {
        var users = await _userManager.Users
            .Include(x => x.UserRoles).ThenInclude(x => x.Role)
            .Include(x => x.Superior)
            .ToListAsync();

        foreach (var user in users)
        {
            var roles = await _userManager.GetRolesAsync(user);
            var subordinateCount = await _userManager.Users.Where(x => x.SuperiorId == user.Id).CountAsync();
            var orgItem = new OrgItem
            {
                Id = user.Id,
                Name = user.DisplayName ?? user.UserName,
                Area = user.Tenant?.Name,
                ProfileUrl = user.ProfilePictureDataUrl,
                ImageUrl = user.ProfilePictureDataUrl,
                IsLoggedUser = _currentUserName == user.UserName,
                Size = string.Empty,
                Tags = user.PhoneNumber ?? user.Email,
                PositionName = roles != null && roles.Count > 0 ? string.Join(',', roles) : string.Empty,
                ParentId = user.SuperiorId,
                DirectSubordinates = subordinateCount
            };
            _orgData.Add(orgItem);
        }

        await new OrgChart(JS).Create(_orgData);
    }

    #endregion

    #region File Upload Method

    private async Task UploadPhoto(IBrowserFile file)
    {
        using var fileStream = file.OpenReadStream(GlobalVariable.MaxAllowedSize);
        using var memoryStream = new MemoryStream();
        await fileStream.CopyToAsync(memoryStream);
        byte[] fileData = memoryStream.ToArray();

        var user = await _userManager.FindByNameAsync(_profileModel.UserName)
                   ?? throw new NotFoundException($"User [{_profileModel.UserName}] not found.");
        var resizedStream = await ImageProcessor.ResizeAndCompressToJpegAsync(memoryStream, 128, 128, 80);
        var uploadRequest = new UploadRequest(file.Name, UploadType.ProfilePicture, fileData, overwrite: true, user.Id);


        var result = await UploadService.UploadAsync(uploadRequest);
        _profileModel.ProfilePictureDataUrl = result;
        user.ProfilePictureDataUrl = _profileModel.ProfilePictureDataUrl;
        await _userManager.UpdateAsync(user);
        Snackbar.Add(Localizer["The avatar has been updated"], Severity.Info);
        UserProfileStateService.UpdateUserProfile(user.UserName!, user.ProfilePictureDataUrl, user.DisplayName, user.PhoneNumber, user.TimeZoneId, user.LanguageCode);
        await OnlineUserTracker.Update(user.Id, user.UserName ?? "", user.DisplayName ?? "", user.ProfilePictureDataUrl);
    }

    #endregion

    #region Profile Update Submission

    private async Task SubmitProfileAsync()
    {
        _submitting = true;
        try
        {
            await _profileForm!.Validate();
            if (_profileForm.IsValid)
            {
                var user = await _userManager.FindByNameAsync(_currentUserName)
                           ?? throw new NotFoundException($"User [{_currentUserName}] not found.");
                user.PhoneNumber = _profileModel.PhoneNumber;
                user.DisplayName = _profileModel.DisplayName;
                user.TimeZoneId = _profileModel.TimeZoneId;
                user.LanguageCode = _profileModel.LanguageCode;
                user.ProfilePictureDataUrl = _profileModel.ProfilePictureDataUrl;
                await _userManager.UpdateAsync(user);
                UserProfileStateService.UpdateUserProfile(user.UserName!, user.ProfilePictureDataUrl, user.DisplayName, user.PhoneNumber, user.TimeZoneId, user.LanguageCode);
                await OnlineUserTracker.Update(user.Id, user.UserName ?? "", user.DisplayName ?? "", user.ProfilePictureDataUrl ?? "");
                Snackbar.Add(ConstantString.SaveSuccess, Severity.Info);
            }
        }
        finally
        {
            _submitting = false;
        }
    }

    #endregion

    #region Change Password Submission

    private async Task ChangePasswordAsync()
    {
        _submitting = true;
        try
        {
            await _passwordForm!.Validate();
            if (_passwordForm.IsValid)
            {
                var user = await _userManager.FindByNameAsync(_profileModel.UserName)
                           ?? throw new NotFoundException($"User [{_profileModel.UserName}] not found.");
                var result = await _userManager.ChangePasswordAsync(user, _changePasswordModel.CurrentPassword, _changePasswordModel.NewPassword);
                if (result.Succeeded)
                {
                    Snackbar.Add(Localizer["Password changed successfully"], Severity.Info);
                }
                else
                {
                    Snackbar.Add(string.Join(",", result.Errors.Select(e => e.Description)), Severity.Error);
                }
            }
        }
        finally
        {
            _submitting = false;
        }
    }

    #endregion

    #region Two-Factor Authentication Methods

    private async Task SetupTwoFactorAsync()
    {
        var user = await _userManager.FindByNameAsync(_currentUserName)
                   ?? throw new NotFoundException($"User [{_currentUserName}] not found.");

        // Generate the authenticator key and URI
        _sharedKey = await _userManager.GetAuthenticatorKeyAsync(user);

        // If the user doesn't have a key, generate one
        if (string.IsNullOrEmpty(_sharedKey))
        {
            await _userManager.ResetAuthenticatorKeyAsync(user);
            _sharedKey = await _userManager.GetAuthenticatorKeyAsync(user);
        }

        // Create the authenticator URI
        _authenticatorUri = GenerateQrCodeUri(user.Email!, _sharedKey!);

        // Generate QR code image
        _qrCodeImageUrl = await (new GenerateQrCode(JS)).Generate(_authenticatorUri);

        _showSetup = true;
        StateHasChanged();
    }

    private string GenerateQrCodeUri(string email, string unformattedKey)
    {
        const string authenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
        return string.Format(
            authenticatorUriFormat,
            Uri.EscapeDataString("CleanArchitecture.Blazor"),
            Uri.EscapeDataString(email),
            unformattedKey);
    }

    private async Task VerifyTwoFactorSetupAsync()
    {
        if (string.IsNullOrEmpty(_verificationCode) || _verificationCode.Length != 6)
        {
            Snackbar.Add(Localizer["Please enter a valid 6-digit verification code"], Severity.Warning);
            return;
        }

        _submitting = true;
        try
        {
            var user = await _userManager.FindByNameAsync(_currentUserName)
                       ?? throw new NotFoundException($"User [{_currentUserName}] not found.");

            var isTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
                user,
                _userManager.Options.Tokens.AuthenticatorTokenProvider,
                _verificationCode);

            if (isTokenValid)
            {
                // Enable 2FA for the user
                await _userManager.SetTwoFactorEnabledAsync(user, true);

                // Generate recovery codes
                _recoveryCodes = (await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10)).ToList();

                _twoFactorEnabled = true;
                _showSetup = false;

                // Show recovery codes in a dialog
                await ShowRecoveryCodesAsync();

                Snackbar.Add(Localizer["Two-factor authentication has been enabled."], Severity.Success);
            }
            else
            {
                Snackbar.Add(Localizer["Verification code is invalid."], Severity.Error);
            }
        }
        finally
        {
            _submitting = false;
        }
    }

    private async Task DisableTwoFactorAsync()
    {

        var user = await _userManager.FindByNameAsync(_currentUserName)
                   ?? throw new NotFoundException($"User [{_currentUserName}] not found.");

        var result = await _userManager.SetTwoFactorEnabledAsync(user, false);

        if (result.Succeeded)
        {
            _twoFactorEnabled = false;
            Snackbar.Add(Localizer["Two-factor authentication has been disabled."], Severity.Success);
        }
        else
        {
            Snackbar.Add(string.Join(",", result.Errors.Select(e => e.Description)), Severity.Error);
        }

    }

    private async Task ShowRecoveryCodesAsync()
    {
        var user = await _userManager.FindByNameAsync(_currentUserName)
                   ?? throw new NotFoundException($"User [{_currentUserName}] not found.");

        // Get recovery codes (or generate new ones if they haven't been generated yet)
        if (_recoveryCodes.Count == 0)
        {
            _recoveryCodes = (await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10)).ToList();
        }

        // Display the recovery codes in a dialog
        var parameters = new DialogParameters<RecoveryCodesDialog>
        {
            { x=>x.RecoveryCodes, _recoveryCodes },
        };

        var dialogOptions = new DialogOptions
        {
            CloseButton = true,
            MaxWidth = MaxWidth.Small,
            FullWidth = true
        };

        await DialogService.ShowAsync<RecoveryCodesDialog>(Localizer["Recovery Codes"], parameters, dialogOptions);
    }

    #endregion
}
